﻿using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Babylon.Importers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;

namespace Babylon.Toolbox.Sample
{
    public partial class MainPage
    {
        float angle;
        Scene currentScene;
        SceneObject viper;
        ModelMesh selectedMesh;
        readonly ObjImporter importer;

        public enum MouseMode
        {
            ArcRotate,
            ObjectPicking
        }

        MouseMode currentMode = MouseMode.ArcRotate;

        public MainPage()
        {
            InitializeComponent();

            importer = new ObjImporter(SynchronizationContext.Current);
        }

        private void DrawingSurface_Draw(object sender, DrawEventArgs e)
        {
            if (currentScene == null)
            {
                Init(e.GraphicsDevice);
            }

            if (currentScene == null)
                return;

            DateTime start = DateTime.Now;

            // Update viper
            viper.Z = (float)(2 * Math.Cos(angle));
            viper.Y = (float)(0.5 * Math.Sin(angle));
            angle += 0.01f;

            // Draw
            currentScene.Draw(e.TotalTime);

            double duration = (double)DateTime.Now.Subtract(start).Ticks / TimeSpan.TicksPerMillisecond;
            int verticesCount = currentScene.Objects.Sum(o => o.VerticesCount);
            Dispatcher.BeginInvoke(() =>
                                       {
                                           frameTime.Text = string.Format("Frame time : {0:0.00} ms / {1:0.00} fps", duration, 1000 / duration);
                                           counters.Text = string.Format("Vertices count : {0}", verticesCount);
                                       });

            e.InvalidateSurface();
        }

        #region Object loading
        private void Init(GraphicsDevice device)
        {
            Stream objStream = Application.GetResourceStream(new Uri("/Babylon.Toolbox.Sample;component/Models/Viper-mk-IV-fighter.obj", UriKind.Relative)).Stream;
            // Model created event
            importer.OnImportCompleted += m =>
            {
                waitBar.Visibility = Visibility.Collapsed;

                currentScene = new Scene(drawingSurface, device);

                viper = new SceneObject("Viper")
                                        {
                                            Model = m.BuildModel(device, new ModelBuilding.BasicEffectMaterialConverter()),
                                            NeedAlphaBlending = false
                                        };

                viper.OnConfigureEffect += (s, e) =>
                                               {
                                                   BasicEffect effect = e.Effect as BasicEffect;

                                                   effect.SceneAmbientColor = e.Scene.AmbientColor;
                                                   effect.LightPosition = e.Scene.Camera.Position + new Vector3(1, 5, 3);
                                                   effect.CameraPosition = e.Scene.Camera.Position;

                                                   effect.EmissiveColor = selectedMesh == e.ModelMesh ? new Color(1.0f, 0, 0, 1.0f) : new Color(0, 0, 0, 0);
                                               };

                currentScene.Objects.Add(viper);

                currentScene.CreateDefaultOrbitCamera("Chase Camera");

                (currentScene.Camera as OrbitCamera).AttachTo(LayoutRoot);

                // Particles
                currentScene.ParticleSystems.Add(CreateFire(device, new Vector3(13.5f, 1.66f, 0)));
                currentScene.ParticleSystems.Add(CreateSmoke(device, new Vector3(14.5f, 1.66f, 0)));
                currentScene.ParticleSystems.Add(CreateFire(device, new Vector3(13.5f, -0.05f, -2.01f)));
                currentScene.ParticleSystems.Add(CreateSmoke(device, new Vector3(14.5f, -0.05f, -2.01f)));
                currentScene.ParticleSystems.Add(CreateFire(device, new Vector3(13.5f, -0.04f, 2.03f)));
                currentScene.ParticleSystems.Add(CreateSmoke(device, new Vector3(14.5f, -0.04f, 2.03f)));

                drawingSurface.Invalidate();
            };

            // Report loading progress
            importer.OnImportProgressChanged += p =>
            {
                waitProgressBar.Value = p;
            };

            importer.ImportAsync(objStream, GetResourceStream, ImportationOptions.Optimize | ImportationOptions.GeneratePickingInformations);

            // Particles
            Dispatcher.BeginInvoke(() =>
            {
                waitBar.Visibility = Visibility.Visible;
            });
        }

        Stream GetResourceStream(string name)
        {
            try
            {
                return Application.GetResourceStream(new Uri(string.Format("/Babylon.Toolbox.Sample;component/Models/{0}", name), UriKind.Relative)).Stream;
            }
            catch
            {
                Dispatcher.BeginInvoke(() => MessageBox.Show("Unable to find " + name, "Error", MessageBoxButton.OK));
                return null;
            }
        }
        #endregion

        #region Mouse
        private void drawingSurface_MouseMove(object sender, MouseEventArgs e)
        {
            Point currentPosition = e.GetPosition(drawingSurface);

            switch (currentMode)
            {
                case MouseMode.ObjectPicking:
                    if (currentScene == null)
                        return;

                    SceneObject pickedObject;
                    ModelMesh pickedMesh;
                    float distance;

                    if (currentScene.Pick((int)currentPosition.X, (int)currentPosition.Y, out distance, out pickedObject, out pickedMesh))
                    {
                        selectedMesh = pickedMesh;
                    }
                    else
                    {
                        selectedMesh = null;
                    }

                    break;
            }
        }
        #endregion

        private void arcRotateButton_Click(object sender, RoutedEventArgs e)
        {
            currentMode = MouseMode.ArcRotate;

            selectedMesh = null;
        }

        private void objectPickingButton_Click(object sender, RoutedEventArgs e)
        {
            currentMode = MouseMode.ObjectPicking;

            selectedMesh = null;
        }

        #region Particules
        static Texture2D LoadTexture(string resource, GraphicsDevice gd)
        {
            var stream = Application.GetResourceStream(new Uri(resource, UriKind.Relative)).Stream;
            var bmp = new BitmapImage();
            bmp.SetSource(stream);


            Texture2D res = new Texture2D(gd, bmp.PixelWidth, bmp.PixelHeight, false, SurfaceFormat.Color);
            bmp.CopyTo(res);
            return res;
        }

        ParticleSystem CreateSmoke(GraphicsDevice gd, Vector3 position)
        {
            ParticleSettings smokeSettings = new ParticleSettings();

            Texture2D smokeTexture = LoadTexture("smoke.png", gd);

            smokeSettings.MaxParticleCount = 100;
            smokeSettings.Texture = smokeTexture;
            smokeSettings.BlendState = BlendState.AlphaBlend;
            smokeSettings.ParticleAvgDuration = 2;
            smokeSettings.ParticleDurationRandomness = 0.2f;
            smokeSettings.MinStartSize = 0.8f;
            smokeSettings.MaxStartSize = 1.5f;
            smokeSettings.MinEndSize = 3.0f;
            smokeSettings.MaxEndSize = 7.0f;
            smokeSettings.MinVelocityValues = new Vector3(0.6f, 0, 0);
            smokeSettings.MaxVelocityValues = new Vector3(2, 0, 0);
            smokeSettings.EndVelocityRatio = 0.75f;
            smokeSettings.MinRotationSpeed = -1;
            smokeSettings.MaxRotationSpeed = 1;
            smokeSettings.EnvironmentForces = new Vector3(2, 0, 0);
            smokeSettings.MinTeint = new Vector3(1, 1, 1);
            smokeSettings.MaxTeint = new Vector3(1, 1, 1);

            return new ParticleSystem(gd, smokeSettings, position, viper);
        }

        ParticleSystem CreateFire(GraphicsDevice gd, Vector3 position)
        {
            ParticleSettings fireSettings = new ParticleSettings();

            Texture2D fireTexture = LoadTexture("fire.png", gd);

            fireSettings.MaxParticleCount = 60;
            fireSettings.Texture = fireTexture;
            fireSettings.BlendState = BlendState.Additive;
            fireSettings.ParticleAvgDuration = 0.3f;
            fireSettings.ParticleDurationRandomness = 0.5f;
            fireSettings.MinStartSize = 1.3f;
            fireSettings.MaxStartSize = 1.6f;
            fireSettings.MinEndSize = 3.5f;
            fireSettings.MaxEndSize = 4.5f;
            fireSettings.MinVelocityValues = new Vector3(2.0f, 0, 0);
            fireSettings.MaxVelocityValues = new Vector3(2.5f, 0, 0);
            fireSettings.EndVelocityRatio = 1;
            fireSettings.MinRotationSpeed = -1;
            fireSettings.MaxRotationSpeed = 1;
            fireSettings.EnvironmentForces = new Vector3(0.5f, 0, 0);
            fireSettings.MinTeint = new Vector3(1, 0.4f, 0.0f);
            fireSettings.MaxTeint = new Vector3(1, 0.9f, 0.0f);

            return new ParticleSystem(gd, fireSettings, position, viper);
        }
        #endregion

        public SynchronizationContext Synchonization { get; set; }
    }
}